/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2006 - 2013 ScriptDev2 <http://www.scriptdev2.com/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "ScriptPCH.h"
#include "naxxramas.h"

#define EMOTE_BREATH            -1533082
#define EMOTE_ENRAGE            -1533083

#define SPELL_FROST_AURA        RAID_MODE(28531, 55799)
#define SPELL_CLEAVE            19983
#define SPELL_TAIL_SWEEP        RAID_MODE(55697, 55696)
#define SPELL_SUMMON_BLIZZARD   28560
#define SPELL_LIFE_DRAIN        RAID_MODE(28542, 55665)
#define SPELL_ICEBOLT           28522
#define SPELL_FROST_BREATH      29318
#define SPELL_FROST_EXPLOSION   28524
#define SPELL_FROST_MISSILE     30101
#define SPELL_BERSERK           26662
#define SPELL_DIES              29357

#define SPELL_CHILL             RAID_MODE(28547, 55699)

#define MOB_BLIZZARD            16474
#define GO_ICEBLOCK             181247

#define ACHIEVEMENT_THE_HUNDRED_CLUB    RAID_MODE(2146, 2147)
#define MAX_FROST_RESISTANCE            100

enum Phases {
    PHASE_NULL = 0, PHASE_BIRTH, PHASE_GROUND, PHASE_FLIGHT,
};

enum Events {
    EVENT_NONE,
    EVENT_BERSERK,
    EVENT_CLEAVE,
    EVENT_TAIL,
    EVENT_DRAIN,
    EVENT_BLIZZARD,
    EVENT_FLIGHT,
    EVENT_LIFTOFF,
    EVENT_ICEBOLT,
    EVENT_BREATH,
    EVENT_EXPLOSION,
    EVENT_LAND,
    EVENT_GROUND,
    EVENT_BIRTH,
};

typedef std::map<uint64, uint64> IceBlockMap;

class boss_sapphiron: public CreatureScript {
public:
    boss_sapphiron() :
            CreatureScript("boss_sapphiron") {
    }

    CreatureAI* GetAI(Creature* pCreature) const {
        return new boss_sapphironAI(pCreature);
    }

    struct boss_sapphironAI: public BossAI {
        boss_sapphironAI(Creature* c) :
                BossAI(c, BOSS_SAPPHIRON), phase(PHASE_NULL) {
            pMap = me->GetMap();
            me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK,
                    true);
            me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_GRIP, true);
        }

        Phases phase;
        uint32 iceboltCount;
        IceBlockMap iceblocks;

        bool CanTheHundredClub; // needed for achievement: The Hundred Club(2146, 2147)
        uint32 CheckFrostResistTimer;
        Map* pMap;

        void InitializeAI() {
            float x, y, z;
            me->GetPosition(x, y, z);
            me->SummonGameObject(GO_BIRTH, x, y, z, 0, 0, 0, 0, 0, 0);
            me->SetVisible(false);
            me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
            me->SetReactState(REACT_PASSIVE);

            ScriptedAI::InitializeAI();
        }

        void Reset() {
            _Reset();

            if (phase == PHASE_FLIGHT)
                ClearIceBlock();

            phase = PHASE_NULL;

            CanTheHundredClub = true;
            CheckFrostResistTimer = 5000;
        }

        void EnterCombat(Unit * /*who*/) {
            _EnterCombat();

            me->CastSpell(me, SPELL_FROST_AURA, true);

            events.ScheduleEvent(EVENT_BERSERK, 15 * 60000);
            EnterPhaseGround();

            CheckPlayersFrostResist();
        }

        void SpellHitTarget(Unit *pTarget, const SpellEntry *spell) {
            if (spell->Id == SPELL_ICEBOLT) {
                IceBlockMap::iterator itr = iceblocks.find(pTarget->GetGUID());
                if (itr != iceblocks.end() && !itr->second) {
                    if (GameObject *iceblock = me->SummonGameObject(GO_ICEBLOCK, pTarget->GetPositionX(), pTarget->GetPositionY(), pTarget->GetPositionZ(), 0, 0, 0, 0, 0, 25000))
                        itr->second = iceblock->GetGUID();
                }
            }
        }

        void JustDied(Unit* /*who*/) {
            _JustDied();
            me->CastSpell(me, SPELL_DIES, true);

            CheckPlayersFrostResist();
            if (CanTheHundredClub) {
                AchievementEntry const *AchievTheHundredClub =
                        GetAchievementStore()->LookupEntry(
                                ACHIEVEMENT_THE_HUNDRED_CLUB);
                if (AchievTheHundredClub) {
                    if (pMap && pMap->IsDungeon()) {
                        Map::PlayerList const &players = pMap->GetPlayers();
                        for (Map::PlayerList::const_iterator itr =
                                players.begin(); itr != players.end(); ++itr)
                            itr->getSource()->CompletedAchievement(
                                    AchievTheHundredClub);
                    }
                }
            }
        }

        void MovementInform(uint32, uint32 id) {
            if (id == 1)
                events.ScheduleEvent(EVENT_LIFTOFF, 0);
        }

        void DoAction(const int32 param) {
            if (param == DATA_SAPPHIRON_BIRTH) {
                phase = PHASE_BIRTH;
                events.ScheduleEvent(EVENT_BIRTH, 23000);
            }
        }

        void CheckPlayersFrostResist() {
            if (CanTheHundredClub && pMap && pMap->IsDungeon()) {
                Map::PlayerList const &players = pMap->GetPlayers();
                for (Map::PlayerList::const_iterator itr = players.begin();
                        itr != players.end(); ++itr) {
                    if (itr->getSource()->GetResistance(
                            SPELL_SCHOOL_FROST) > MAX_FROST_RESISTANCE) {
                        CanTheHundredClub = false;
                        break;
                    }
                }
            }
        }

        void EnterPhaseGround() {
            phase = PHASE_GROUND;
            me->SetReactState(REACT_AGGRESSIVE);
            events.SetPhase(PHASE_GROUND);
            events.ScheduleEvent(EVENT_CLEAVE, 5000 + rand() % 10000, 0,
                    PHASE_GROUND);
            events.ScheduleEvent(EVENT_TAIL, 5000 + rand() % 10000, 0,
                    PHASE_GROUND);
            events.ScheduleEvent(EVENT_DRAIN, 24000, 0, PHASE_GROUND);
            events.ScheduleEvent(EVENT_BLIZZARD, 5000 + rand() % 5000, 0,
                    PHASE_GROUND);
            events.ScheduleEvent(EVENT_FLIGHT, 45000);
        }

        void ClearIceBlock() {
            for (IceBlockMap::const_iterator itr = iceblocks.begin();
                    itr != iceblocks.end(); ++itr) {
                if (Player* pPlayer = Unit::GetPlayer(*me, itr->first))
                    pPlayer->RemoveAura(SPELL_ICEBOLT);
                if (GameObject* pGo = GameObject::GetGameObject(*me, itr->second))
                    pGo->Delete();
            }
            iceblocks.clear();
        }

        void UpdateAI(const uint32 diff) {
            if (!phase)
                return;

            events.Update(diff);

            if ((phase != PHASE_BIRTH && !UpdateVictim()) || !CheckInRoom())
                return;

            if (CanTheHundredClub) {
                if (CheckFrostResistTimer <= diff) {
                    CheckPlayersFrostResist();
                    CheckFrostResistTimer = (rand() % 5 + 5) * 1000;
                } else
                    CheckFrostResistTimer -= diff;
            }

            if (phase == PHASE_GROUND) {
                while (uint32 eventId = events.ExecuteEvent()) {
                    switch (eventId) {
                    case EVENT_BERSERK:
                        DoScriptText(EMOTE_ENRAGE, me);
                        DoCast(me, SPELL_BERSERK);
                        return;
                    case EVENT_CLEAVE:
                        DoCast(me->getVictim(), SPELL_CLEAVE);
                        events.ScheduleEvent(EVENT_CLEAVE,
                                5000 + rand() % 10000, 0, PHASE_GROUND);
                        return;
                    case EVENT_TAIL:
                        DoCastAOE(SPELL_TAIL_SWEEP);
                        events.ScheduleEvent(EVENT_TAIL, 5000 + rand() % 10000,
                                0, PHASE_GROUND);
                        return;
                    case EVENT_DRAIN:
                        DoCastAOE(SPELL_LIFE_DRAIN);
                        events.ScheduleEvent(EVENT_DRAIN, 24000, 0,
                                PHASE_GROUND);
                        return;
                    case EVENT_BLIZZARD: {
                        //DoCastAOE(SPELL_SUMMON_BLIZZARD);
                        if (Creature *pSummon = DoSummon(MOB_BLIZZARD, me, 0.0f, urand(25000, 30000), TEMPSUMMON_TIMED_DESPAWN))
                            pSummon->GetMotionMaster()->MoveRandom(40);
                        events.ScheduleEvent(EVENT_BLIZZARD,
                                RAID_MODE(20000, 7000), 0, PHASE_GROUND);
                        break;
                    }
                    case EVENT_FLIGHT:
                        phase = PHASE_FLIGHT;
                        events.SetPhase(PHASE_FLIGHT);
                        me->SetReactState(REACT_PASSIVE);
                        me->AttackStop();
                        float x, y, z, o;
                        me->GetHomePosition(x, y, z, o);
                        me->GetMotionMaster()->MovePoint(1, x, y, z);
                        return;
                    }
                }

                DoMeleeAttackIfReady();
            } else {
                if (uint32 eventId = events.ExecuteEvent()) {
                    switch (eventId) {
                    case EVENT_LIFTOFF:
                        me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF);
                        me->AddUnitMovementFlag(MOVEMENTFLAG_LEVITATING);
                        me->SendMovementFlagUpdate();
                        events.ScheduleEvent(EVENT_ICEBOLT, 1500);
                        iceboltCount = RAID_MODE(2, 3);
                        return;
                    case EVENT_ICEBOLT: {
                        std::vector<Unit*> targets;
                        std::list<HostileReference*>::const_iterator i =
                                me->getThreatManager().getThreatList().begin();
                        for (;
                                i
                                        != me->getThreatManager().getThreatList().end();
                                ++i)
                            if ((*i)->getTarget()->GetTypeId() == TYPEID_PLAYER
                                    && !(*i)->getTarget()->HasAura(
                                            SPELL_ICEBOLT))
                                targets.push_back((*i)->getTarget());

                        if (targets.empty())
                            iceboltCount = 0;
                        else {
                            std::vector<Unit*>::const_iterator itr =
                                    targets.begin();
                            advance(itr, rand() % targets.size());
                            iceblocks.insert(
                                    std::make_pair((*itr)->GetGUID(), 0));
                            DoCast(*itr, SPELL_ICEBOLT);
                            --iceboltCount;
                        }

                        if (iceboltCount)
                            events.ScheduleEvent(EVENT_ICEBOLT, 1000);
                        else
                            events.ScheduleEvent(EVENT_BREATH, 1000);
                        return;
                    }
                    case EVENT_BREATH: {
                        DoScriptText(EMOTE_BREATH, me);
                        DoCastAOE(SPELL_FROST_MISSILE);
                        events.ScheduleEvent(EVENT_EXPLOSION, 8000);
                        return;
                    }
                    case EVENT_EXPLOSION:
                        CastExplosion();
                        ClearIceBlock();
                        events.ScheduleEvent(EVENT_LAND, 3000);
                        return;
                    case EVENT_LAND:
                        me->HandleEmoteCommand(EMOTE_ONESHOT_LAND);
                        me->RemoveUnitMovementFlag(MOVEMENTFLAG_LEVITATING);
                        me->SendMovementFlagUpdate();
                        events.ScheduleEvent(EVENT_GROUND, 1500);
                        return;
                    case EVENT_GROUND:
                        EnterPhaseGround();
                        return;
                    case EVENT_BIRTH:
                        me->SetVisible(true);
                        me->RemoveFlag(UNIT_FIELD_FLAGS,
                                UNIT_FLAG_NON_ATTACKABLE);
                        me->SetReactState(REACT_AGGRESSIVE);
                        return;
                    }
                } //if (uint32 eventId = events.ExecuteEvent())
            } //if (phase == PHASE_GROUND)
        }

        void CastExplosion() {
            DoZoneInCombat(); // make sure everyone is in threatlist
            std::vector<Unit*> targets;
            std::list<HostileReference*>::const_iterator i =
                    me->getThreatManager().getThreatList().begin();
            for (; i != me->getThreatManager().getThreatList().end(); ++i) {
                Unit *pTarget = (*i)->getTarget();
                if (pTarget->GetTypeId() != TYPEID_PLAYER)
                    continue;

                if (pTarget->HasAura(SPELL_ICEBOLT)) {
                    pTarget->ApplySpellImmune(0, IMMUNITY_ID,
                            SPELL_FROST_EXPLOSION, true);
                    targets.push_back(pTarget);
                    continue;
                }

                for (IceBlockMap::const_iterator itr = iceblocks.begin();
                        itr != iceblocks.end(); ++itr) {
                    if (GameObject* pGo = GameObject::GetGameObject(*me, itr->second)) {
                        if (pGo->IsInBetween(me, pTarget, 2.0f)
                                && me->GetExactDist2d(pTarget->GetPositionX(),
                                        pTarget->GetPositionY())
                                        - me->GetExactDist2d(
                                                pGo->GetPositionX(),
                                                pGo->GetPositionY()) < 5.0f) {
                            pTarget->ApplySpellImmune(0, IMMUNITY_ID,
                                    SPELL_FROST_EXPLOSION, true);
                            targets.push_back(pTarget);
                            break;
                        }
                    }
                }
            }

            me->CastSpell(me, SPELL_FROST_EXPLOSION, true);

            for (std::vector<Unit*>::const_iterator itr = targets.begin();
                    itr != targets.end(); ++itr)
                (*itr)->ApplySpellImmune(0, IMMUNITY_ID, SPELL_FROST_EXPLOSION,
                        false);
        }
    };
};

void AddSC_boss_sapphiron() {
    new boss_sapphiron();
}
